#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "elftool.h"
#include "memsim.h"
#include "isa.h"

#define TAG_MATCH 0
#define TAG_STEP 1
#define TAG_REG 2
#define TAG_MEMREAD 3 
#define TAG_MEMWRITE 4

//////////////////////////////////////////////////////////////////
// Execution Related

unsigned int fetch(unsigned int* data, unsigned int offset, unsigned int size, unsigned int addr) {
	if ( addr < offset || addr >= offset+size ) {
//		printf ( " %x ~ %x %x\n", addr, offset, size );
		return 0;
	}

	return reverse_uint(data[(addr-offset)>>2]);
}

signed int reg[32];

unsigned int next_pc(unsigned int pc, instr i) {
	switch ( i.opcode ) {
		case J: return i.target << 2;
		case JAL: 
			reg[31] = pc + 4;
			return i.target << 2;
		case JR:
			if ( i.fn == F_JALR) {
				reg[i.rd] = 0;//pc + 4;
				return i.rs;
			} else if ( i.fn == F_JR ) {
				return i.rs;
			}
			return 0;//pc + 4;			
		case BEQ:
			if ( reg[i.rs] == reg[i.rt] ) return pc + 4 + (i.imm<<2);
			else return 0;//pc + 4;
		case BNE:
			if ( reg[i.rs] != reg[i.rt] ) return pc + 4 + ( i.imm<<2);
			else return 0;//pc + 4;
		case BLEZ:
			if ( reg[i.rs] <= 0 ) return pc + 4 + (i.imm<<2);
			else return 0;//pc + 4;
		case BGTZ:
			if ( reg[i.rs] > 0 ) return pc + 4 + ( i.imm<<2);
			else return 0;//pc + 4;
		case BMORE:
			if (i.rd == D_BLTZ ) {
				if ( reg[i.rs] < 0 ) return pc + 4 + ( i.imm<<2);
				else return 0;//pc + 4;
			} else if (i.rd == D_BGEZ) {
				if ( reg[i.rs] >= 0 ) return pc + 4 + ( i.imm<<2);
				else return 0;//pc + 4;
			}
			return 0;
		default: return 0;
	}

	fprintf(stderr, "error: unhandled branch instruction!\n");
	return 0;
}

int alu(instr i) {
	switch(i.opcode) {
		case ADDI: reg[i.rt] = reg[i.rs] + i.imm; 
			//TODO: handle overflow!
			return 0;
		case ADDIU: reg[i.rt] = reg[i.rs] + i.imm; return 0;
		case SLTI: reg[i.rt] = reg[i.rs] < i.imm ? 1 : 0; return 0;
		case SLTIU: reg[i.rt] = reg[i.rs] < (unsigned short)i.imm ? 1 : 0; return 0;
		case ANDI: reg[i.rt] = reg[i.rs] & i.imm; return 0;
		case ORI: reg[i.rt] = reg[i.rs] | i.imm; return 0;
		case XORI: reg[i.rt] = reg[i.rs] ^ i.imm; return 0;
		case LUI: reg[i.rt] = i.imm << 16; return 0;
		case 0: {
			switch(i.fn) {
				case F_SLL: reg[i.rd] = reg[i.rt]<<i.sh; return 0;
				case F_SRL: reg[i.rd] = reg[i.rt]>>i.sh; return 0;
				case F_SRA: return 1; // TODO
				case F_SRAV: return 1; // TODO
				case F_SLLV: reg[i.rd] = reg[i.rt]<<reg[i.rs]; return 0;
				case F_SRLV: reg[i.rd] = reg[i.rt]>>reg[i.rs]; return 0;

				case F_ADD: reg[i.rd] = reg[i.rs] + reg[i.rt]; return 0;
				case F_ADDU: reg[i.rd] = (unsigned int)reg[i.rs] + (unsigned int)reg[i.rt]; return 0;
				case F_SUB: reg[i.rd] = reg[i.rs] - reg[i.rt]; return 0;
				case F_SUBU: reg[i.rd] = (unsigned int)reg[i.rs] - (unsigned int)reg[i.rt]; return 0;
				
				case F_AND: reg[i.rd] = reg[i.rs] & reg[i.rt]; return 0;
				case F_OR: reg[i.rd] = reg[i.rs] | reg[i.rt]; return 0;
				case F_XOR: reg[i.rd] = reg[i.rs] ^ reg[i.rt]; return 0;
				case F_NOR: reg[i.rd] = ~(reg[i.rs] | reg[i.rt]); return 0;

				case F_SLT: reg[i.rd] = reg[i.rs] < reg[i.rt] ? 1 : 0; return 0;
				case F_SLTU: reg[i.rd] = (unsigned int)reg[i.rs] < (unsigned int)reg[i.rt] ? 1 : 0; return 0;
			}
			return 1;
		}
	}

	return 1;
}

enum {
	MEMOP_NONE = 0,
	MEMOP_READ = 1,
	MEMOP_WRITE = 2,
};
unsigned int mem_addr = 0;
unsigned char mem_size = 0;
unsigned char mem_op = 0;
unsigned int mem_writeval = 0;

int mem(instr i) {
	unsigned int addr;
	switch(i.opcode) {
		// LB and LBU is implemented, but is not part of smips implementation...
		case LB:
			addr = reg[i.rs] + i.imm;
			reg[i.rt] = (signed char)mem_LB(addr);
			mem_op = MEMOP_READ;
			mem_addr = addr;
			mem_size = 1;
			return 0;	
		case LBU:
			addr = reg[i.rs] + i.imm;
			reg[i.rt] = (unsigned char)mem_LB(addr);
			mem_op = MEMOP_READ;
			mem_addr = addr;
			mem_size = 1;
			return 0;
		case LH:
			addr = reg[i.rs] + i.imm;
			reg[i.rt] = (signed short)mem_LHU(addr);
			mem_op = MEMOP_READ;
			mem_addr = addr;
			mem_size = 2;
			return 0;	
		case LHU:
			addr = reg[i.rs] + i.imm;
			reg[i.rt] = mem_LHU(addr);
			mem_op = MEMOP_READ;
			mem_addr = addr;
			mem_size = 2;
			return 0;	
		case LW: 
			addr = reg[i.rs] + i.imm;
			reg[i.rt] = mem_LW(addr);

			mem_op = MEMOP_READ;
			mem_addr = addr;
			mem_size = 4;

			return 0;
		case SB:
			addr = reg[i.rs] + i.imm;
			mem_SB(addr, reg[i.rt]);
			mem_op = MEMOP_WRITE;
			mem_addr = addr;
			mem_size = 1;
			mem_writeval = reg[i.rt];
			return 0;
		case SH:
			addr = reg[i.rs] + i.imm;
			mem_SH(addr, reg[i.rt]);
			mem_op = MEMOP_WRITE;
			mem_addr = addr;
			mem_size = 2;
			mem_writeval = reg[i.rt];
			return 0;
		case SW:
			addr = reg[i.rs] + i.imm;
			mem_SW(addr, reg[i.rt]);
			
			mem_op = MEMOP_WRITE;
			mem_addr = addr;
			mem_size = 4;

			mem_writeval = reg[i.rt];
			return 0;
	}

	return 1;
}

///////////////////////////////////////////////
// Helper functions

unsigned int atox(char* c) {
	unsigned int v = 0;
	while (*c) {
		if ( *c >= 'a' && *c <= 'z' ) {
			v *= 0x10;
			v += *c - 'a' + 0xa;
		} else if ( *c >= 'A' && *c <= 'Z' ) {
			v *= 0x10;
			v += *c - 'A' + 0xa;
		} else if ( *c >= '0' && *c <= '9' ) {
			v *= 0x10;
			v += *c - '0';
		} else {
			return v;
		}
		c += 1;
	}
	return v;
}

unsigned int pred_branch_delay = 0;

FILE* fdinero;
void output_dinero(unsigned int pc, unsigned int addr, int memop) {
	if ( !fdinero ) {
		fdinero = fopen("dinerofifo", "w");
		if ( !fdinero ) return;
	}

	fprintf(fdinero, "2 %x\n", pc );
	if ( memop == MEMOP_READ ) {
		fprintf(fdinero, "0 %x\n", addr);
	} else if ( memop == MEMOP_WRITE ) {
		fprintf(fdinero, "1 %x\n", addr);
	}
}

FILE* fdecompr_bin = 0;
void output_decompr_bin(unsigned int flags, unsigned int addr, unsigned int value, unsigned int pc) 
{
	if ( !fdecompr_bin ) fdecompr_bin = fopen("decompressed.bin", "wb");
	if ( !fdecompr_bin ) return;

	fwrite(&flags, sizeof(unsigned int), 1, fdecompr_bin); 
	fwrite(&addr, sizeof(unsigned int), 1, fdecompr_bin);
	fwrite(&value, sizeof(unsigned int), 1, fdecompr_bin);
	fwrite(&pc, sizeof(unsigned int), 1, fdecompr_bin);
}


unsigned int decompr_count = 0;
FILE* fdecompr = 0;
void output_decompr(unsigned int flags, unsigned int addr, unsigned int value, unsigned int pc) 
{
	decompr_count++;
	if ( decompr_count % 1000000 == 0) printf( "%d mil. instructions decompressed\n", decompr_count/1000000 );
	//output_decompr_bin(flags, addr, value, pc);
	unsigned char regChange = (flags>>16)> 0 ? 1 : 0;
	unsigned char memAcc = (flags>>17)%4;
	unsigned char size = (flags>>19)%(1<<3);
	if ( size == 3 ) size = 4;
	unsigned char target = (flags>>24)%(1<<5);
	unsigned char done = (flags>>31)>0 ? 1 : 0;
	unsigned int nflags = ((regChange%2)<<16) + (mem_op<<17) + ((mem_size%(1<<3))<<19) + (target<<24);
	output_decompr_bin(nflags, addr, value, pc);
//	output_dinero(pc, addr, memAcc);
	if ( decompr_count > 102000000 ) exit(1);
	return;






	if ( !fdecompr ) fdecompr = fopen("trace.log", "w");
	if ( !fdecompr ) return;


	if ( memAcc == 1 ) {
		fprintf(fdecompr, "MEM_READ %012x %02d %x\n", addr, size, value);
	} else if ( memAcc == 2 ) {
		fprintf(fdecompr, "MEM_WRITE %012x %02d %x\n", addr, size, value);
	}

	if ( regChange ) {
		fprintf(fdecompr, "REG %02d 00 %x\n", target, value);
	}

	fprintf(fdecompr, "STEP%x\n#\n", pc );
}

#define HASH_SIZE 1024
//total meaningful bits: 1 + 2 + 2 + 5 = 10 => 1024 entries

typedef struct {
	unsigned int flag;
	unsigned int addr;
	unsigned int value;
	unsigned int pc;

	unsigned int addrd;
	unsigned int valued;
	unsigned int pcd;
} type_last;
type_last* type_hash = 0;

int main(int argc, char** argv) {
	FILE* fexec = fopen( "exec.bin", "rb" );
	FILE* fcompressed = fopen ( "compressed.bin", "rb" );
	//fdecompr = fopen( "trace.log", "w" );
	int secondorder_instr = 0;

	if ( argc > 1 ) {
		// should check for flags... but oh well
		secondorder_instr = 1;
	}

	unsigned int entry;
	unsigned int isize;
	unsigned int ioffset;
	fread(&entry, sizeof(unsigned int), 1, fexec);
	fread(&ioffset, sizeof(unsigned int), 1, fexec);
	fread(&isize, sizeof(unsigned int), 1, fexec);
	unsigned int *idata = (unsigned int*)calloc(sizeof(unsigned int), isize>>2);
	fread(idata, sizeof(unsigned int), isize>>2, fexec);

	if ( !fexec || !fcompressed /*|| !fdecompr*/) {
		printf("Cannot open input files\n");
		exit(1);
	}
	type_hash = (type_last*)calloc(HASH_SIZE, sizeof(type_last));
	memset(type_hash, 0, sizeof(type_last)*HASH_SIZE);
	mem_init();


	unsigned int readcount = 0;
	//XXX pc should be set automatically?!
	unsigned int pc = 0x4001d0;
	unsigned int flags;
	instr inst;
	printf( "Starting!!\n" );
	while(!feof(fcompressed)) {
		mem_op = MEMOP_NONE;
		fread(&flags, sizeof(unsigned int), 1, fcompressed);
		unsigned int matchcount = flags % (1 << 16);
		unsigned char regChange = (flags>>16)> 0 ? 1 : 0;
		unsigned char memAcc = (flags>>17)%4;
		unsigned char size = (flags>>19)%(1<<2);
		if ( size == 3 ) size = 4;
		unsigned char target = (flags>>24)%(1<<5);
		unsigned char done = (flags>>31)>0 ? 1 : 0;
		unsigned char longMatch = (flags>>30)&1;
		if ( done ) break;
		
		//printf( "%x-- %x %d\n", flags, matchcount, longMatch );

		int pos = ftell(fcompressed);
		mem_clear();
		
		//if ( pos % (1024*1024) == 0 ) printf( "At %d MB\n", pos / 1024/1024 );

		reg[0] = 0;
		
		unsigned int repeat = matchcount;
		if ( longMatch ) repeat = flags % (1 <<30);
		int i;
//		if ( repeat ) printf( "\tpredicting %d\n", repeat );

		for ( i = 0; i < repeat; i++ ) {
			mem_op = MEMOP_NONE;
			mem_clear();
			reg[0] = 0;
			unsigned int pred_ic = fetch(idata, ioffset, isize, pc);
			//printf( "ic: %x @%x\n", pred_ic, pc );

			//decode
			decode(pred_ic, &inst);

			//execute
			reg[0] = 0;
			int reg_old[32] = {0,};
			int j;
			for ( j = 0; j < 32; j++ ) reg_old[j] = reg[j];

			int ret = mem(inst);
			if ( ret ) {
				ret = alu(inst);
			}
			
			if ( pred_branch_delay ) {
				pc = pred_branch_delay;
				pred_branch_delay = 0;
			} else {
				if ( ret ) pred_branch_delay = next_pc(pc, inst);
				pc = pc + 4;
			}
			mem_commit();
			//output_dinero(pc, mem_addr, mem_op);

			// push out predictions
			unsigned char regChange = 0;
			unsigned char target = 0;
			for ( j = 0; j < 32; j++) {
				if ( reg_old[j] != reg[j] ) {
					regChange = 1;
					target = j;
					//printf( "regchange %d\n", target );
				}
			}
//			if ( mem_op > 0 ) 
//				printf( "mem_size: %d\n", mem_size );
			unsigned int nflags = ((regChange%2)<<16) + (mem_op<<17) + ((mem_size%(1<<3))<<19) + (target<<24);
			unsigned int value = (mem_op == MEMOP_WRITE) ? mem_writeval :
				regChange? reg[target] : 0;
			output_decompr(nflags, mem_addr, value, pc);
		}

		mem_clear();
		if ( !longMatch ) {
			//printf ( "streaming\n" );
			
			char packsize = size;
			if ( packsize == 4 ) packsize = 3;
			unsigned int idx = (regChange%2) + ((memAcc%4)<<1) + ((packsize%4)<<3) + ((target%32)<<5);
			type_last* hashv = &type_hash[idx%HASH_SIZE]; // mod by HASH_SIZE just in case

			unsigned int r_addr = 0;
			unsigned int r_value = 0;
			unsigned int r_pc = 0;

			if ( (flags>> 21)&1 ) {
				r_addr = hashv->addr + hashv->addrd;
			} else if ( memAcc ) {
				fread(&r_addr, sizeof(unsigned int), 1, fcompressed);
			}

			if ( (flags>>22)&1 ) {
				r_value = hashv->value + hashv->valued;
			} else if ( regChange ) {
				fread(&r_value, sizeof(unsigned int), 1, fcompressed);
			}

			if ( (flags>>23)&1 ) {
				r_pc = hashv->pc + hashv->pcd;
			} else {
				fread(&r_pc, sizeof(unsigned int), 1, fcompressed);
			}
			readcount++;

			pc = r_pc;
			
			int addrd = (int)r_addr - hashv->addr;
			int valued = (int)r_value - hashv->value;
			int pcd = (int)r_pc - hashv->pc;
			
			hashv->addr = r_addr;
			hashv->value = r_value;
			hashv->pc = r_pc;

			hashv->addrd = addrd;
			hashv->valued = valued;
			hashv->pcd = pcd;
			

			if ( regChange ) {
				reg[target] = r_value;
			}
			if ( memAcc == 1 ) { // memAccRead
				if ( size == 1 ) {
					mem_LB(r_addr);
				} else if ( size == 2 ) {
					mem_LHU(r_addr);
				} else {// if ( size == 4 ) {
					mem_LW(r_addr);
				}
				mem_commit();
			} else if ( memAcc == 2 ) { // memAccWrite
				if ( size == 1 ) {
					mem_SB(r_addr, r_value);
				} else if ( size == 2 ) {
					mem_SH(r_addr, r_value);
				} else {// if ( size == 4 ) {
					mem_SW(r_addr, r_value);
				}
				mem_commit();
			}
		
			//output_dinero(pc, r_addr, memAcc);
			output_decompr(flags, r_addr, r_value, pc);
		}

		//printf( "predicted %d times\n", matchcount );
	}
	printf( "Done!!\n" );
	exit(0);
}
